// Based on MAME's 6821pia.c
// - by Kyle Kim (Apple in PC)

//
// From mame.txt (http://www.mame.net/readme.html)
//
// VI. Reuse of Source Code
// --------------------------
//    This chapter might not apply to specific portions of MAME (e.g. CPU
//    emulators) which bear different copyright notices.
//    The source code cannot be used in a commercial product without the written
//    authorization of the authors. Use in non-commercial products is allowed, and
//    indeed encouraged.  If you use portions of the MAME source code in your
//    program, however, you must make the full source code freely available as
//    well.
//    Usage of the _information_ contained in the source code is free for any use.
//    However, given the amount of time and energy it took to collect this
//    information, if you find new information we would appreciate if you made it
//    freely available as well.
//
#include "stdafx.h"
#include "6821.h"

// Ctrl-A(B) register bit mask define
/*
      0        1
  bit0  IRQ1_DISABLED  IRQ1_ENABLED
  bit1  C1_HIGH_TO_LOW  C1_LOW_TO_HIGH
  bit2  DDR_SELECTED  OUTPUT_SELECTED

  bit3  RESET_C2    SET_C2        ( C2_OUTPUT & C2_SETMODE )
  bit3  STROBE_C1_RESET  STROBE_E_RESET    ( C2_OUTPUT & C2_STROBE_MODE )
  bit4  C2_STROBE_MODE  C2_SETMODE      ( C2_OUTPUT )

  bit3  IRQ2_DISABLED  IRQ2_ENABLED    ( C2_INPUT )
  bit4  C2_HIGH_TO_LOW  C2_HIGH_TO_LOW    ( C2_INPUT )
  bit5  C2_INPUT    C2_OUTPUT
*/
#define PIA_IRQ1  0x80
#define PIA_IRQ2  0x40
#define SET_IRQ1(c)      c |= PIA_IRQ1;
#define SET_IRQ2(c)      c |= PIA_IRQ2;
#define CLEAR_IRQ1(c)    c &= ~PIA_IRQ1;
#define CLEAR_IRQ2(c)    c &= ~PIA_IRQ2;
#define IRQ1(c)        ( c & PIA_IRQ1 )
#define IRQ2(c)        ( c & PIA_IRQ2 )

#define IRQ1_ENABLED(c)     ( c & 0x01 )
#define IRQ1_DISABLED(c)  !( c & 0x01 )
#define C1_LOW_TO_HIGH(c)   ( c & 0x02 )
#define C1_HIGH_TO_LOW(c)  !( c & 0x02 )
#define OUTPUT_SELECTED(c)   ( c & 0x04 )
#define DDR_SELECTED(c)    !( c & 0x04 )
#define IRQ2_ENABLED(c)     ( c & 0x08 )
#define IRQ2_DISABLED(c)  !( c & 0x08 )
#define STROBE_E_RESET(c)   ( c & 0x08 )
#define STROBE_C1_RESET(c)  !( c & 0x08 )
#define SET_C2(c)       ( c & 0x08 )
#define RESET_C2(c)      !( c & 0x08 )
#define C2_LOW_TO_HIGH(c)   ( c & 0x10 )
#define C2_HIGH_TO_LOW(c)  !( c & 0x10 )
#define C2_SET_MODE(c)     ( c & 0x10 )
#define C2_STROBE_MODE(c)  !( c & 0x10 )
#define C2_OUTPUT(c)     ( c & 0x20 )
#define C2_INPUT(c)      !( c & 0x20 )

#define PIA_W_CALLBACK(st, val)  \
  if ( st.func ) st.func( this, st.objTo, 0, val )

//////////////////////////////////////////////////////////////////////

C6821::C6821()
{
  Reset();
  m_stOutA.objTo = NULL;
  m_stOutA.func = NULL;
  m_stOutB.objTo = NULL;
  m_stOutB.func = NULL;
  m_stOutCA2.objTo = NULL;
  m_stOutCA2.func = NULL;
  m_stOutCB2.objTo = NULL;
  m_stOutCB2.func = NULL;
  m_stOutIRQA.objTo = NULL;
  m_stOutIRQA.func = NULL;
  m_stOutIRQB.objTo = NULL;
  m_stOutIRQB.func = NULL;
}

C6821::~C6821()
{

}

void C6821::SetListenerA(void *objTo, mem_write_handler func)
{
  m_stOutA.objTo = objTo;
  m_stOutA.func = func;
}

void C6821::SetListenerB(void *objTo, mem_write_handler func)
{
  m_stOutB.objTo = objTo;
  m_stOutB.func = func;
}

void C6821::SetListenerCA2(void *objTo, mem_write_handler func)
{
  m_stOutCA2.objTo = objTo;
  m_stOutCA2.func = func;
}

void C6821::SetListenerCB2(void *objTo, mem_write_handler func)
{
  m_stOutCB2.objTo = objTo;
  m_stOutCB2.func = func;
}

unsigned char C6821::Read(unsigned char byRS)
{
  unsigned char retval = 0;
  byRS &= 3;
  switch (byRS) {
    /******************* port A output/DDR read *******************/
    case PIA_DDRA:
      // read output register
      if (OUTPUT_SELECTED(m_byCTLA)) {
        // combine input and output values
        retval = (m_byOA & m_byDDRA) | (m_byIA & ~m_byDDRA);
        // IRQ flags implicitly cleared by a read
        CLEAR_IRQ1(m_byCTLA);
        CLEAR_IRQ1(m_byCTLB);
        UpdateInterrupts();
        // CA2 is configured as output and in read strobe mode
        if (C2_OUTPUT(m_byCTLA) && C2_STROBE_MODE(m_byCTLA)) {
          // this will cause a transition low; call the output function if we're currently high
          if (m_byOCA2)
            PIA_W_CALLBACK(m_stOutCA2, 0);
          m_byOCA2 = 0;

          // if the CA2 strobe is cleared by the E, reset it right away
          if (STROBE_E_RESET(m_byCTLA)) {
            PIA_W_CALLBACK(m_stOutCA2, 1);
            m_byOCA2 = 1;
          }
        }
      }
        // read DDR register
      else {
        retval = m_byDDRA;
      }
      break;

      /******************* port B output/DDR read *******************/
    case PIA_DDRB:

      // read output register
      if (OUTPUT_SELECTED(m_byCTLB)) {
        // combine input and output values
        retval = (m_byOB & m_byDDRB) + (m_byIB & ~m_byDDRB);

        // IRQ flags implicitly cleared by a read
        CLEAR_IRQ2(m_byCTLA);
        CLEAR_IRQ2(m_byCTLB);
        UpdateInterrupts();
      }
        /* read DDR register */
      else {
        retval = m_byDDRB;
      }
      break;

      /******************* port A control read *******************/
    case PIA_CTLA:
      // read control register
      retval = m_byCTLA;
      // when CA2 is an output, IRQA2 = 0, and is not affected by CA2 transitions.
      if (C2_OUTPUT(m_byCTLA))
        retval &= ~PIA_IRQ2;
      break;

      /******************* port B control read *******************/
    case PIA_CTLB:
      retval = m_byCTLB;
      // when CB2 is an output, IRQB2 = 0, and is not affected by CB2 transitions.
      if (C2_OUTPUT(m_byCTLB))
        retval &= ~PIA_IRQ2;
      break;

  }

  return retval;
}

void C6821::Write(unsigned char byRS, unsigned char byData)
{
  byRS &= 3;

  switch (byRS) {
    /******************* port A output/DDR write *******************/
    case PIA_DDRA:

      // write output register
      if (OUTPUT_SELECTED(m_byCTLA)) {
        // update the output value
        m_byOA = byData;

        // send it to the output function
        if (m_byDDRA)
          PIA_W_CALLBACK(m_stOutA, m_byOA & m_byDDRA);
      }

        // write DDR register
      else {
        if (m_byDDRA != byData) {
          m_byDDRA = byData;

          // send it to the output function
          if (m_byDDRA)
            PIA_W_CALLBACK(m_stOutA, m_byOA & m_byDDRA);
        }
      }
      break;

      /******************* port B output/DDR write *******************/
    case PIA_DDRB:

      // write output register
      if (OUTPUT_SELECTED(m_byCTLB)) {
        // update the output value
        m_byOB = byData;

        // send it to the output function
        if (m_byDDRB)
          PIA_W_CALLBACK(m_stOutB, m_byOB & m_byDDRB);

        // CB2 is configured as output and in write strobe mode
        if (C2_OUTPUT(m_byCTLB) && C2_STROBE_MODE(m_byCTLB)) {
          // this will cause a transition low; call the output function if we're currently high
          if (m_byOCB2)
            PIA_W_CALLBACK(m_stOutCB2, 0);
          m_byOCB2 = 0;

          // if the CB2 strobe is cleared by the E, reset it right away
          if (STROBE_E_RESET(m_byCTLB)) {
            PIA_W_CALLBACK(m_stOutCB2, 1);
            m_byOCB2 = 1;
          }
        }
      }
        // write DDR register
      else {
        if (m_byDDRB != byData) {
          m_byDDRB = byData;

          // send it to the output function
          if (m_byDDRB)
            PIA_W_CALLBACK(m_stOutB, m_byOB & m_byDDRB);
        }
      }
      break;

      /******************* port A control write *******************/
    case PIA_CTLA:
      // Bit 7 and 6 read only
      byData &= 0x3f;

      // CA2 is configured as output and in set/reset mode
      if (C2_OUTPUT(byData)) {
        // determine the new value
        int temp = SET_C2(byData) ? 1 : 0;

        // if this creates a transition, call the CA2 output function
        if (m_byOCA2 ^ temp)
          PIA_W_CALLBACK(m_stOutCA2, temp);

        // set the new value
        m_byOCA2 = temp;
      }

      // update the control register
      m_byCTLA = (m_byCTLA & ~0x3F) | byData;

      // update externals
      UpdateInterrupts();
      break;

      /******************* port B control write *******************/
    case PIA_CTLB:

      /* Bit 7 and 6 read only - PD 16/01/00 */

      byData &= 0x3f;

      // CB2 is configured as output and in set/reset mode
      if (C2_OUTPUT(byData)) {
        // determine the new value
        int temp = SET_C2(byData) ? 1 : 0;

        // if this creates a transition, call the CA2 output function
        if (m_byOCB2 ^ temp)
          PIA_W_CALLBACK(m_stOutCB2, temp);

        // set the new value
        m_byOCB2 = temp;
      }

      // update the control register
      m_byCTLB = (m_byCTLB & ~0x3F) | byData;

      // update externals
      UpdateInterrupts();
      break;
  }

}

void C6821::Reset()
{
  m_byIA = 0;
  m_byCA1 = 0;
  m_byICA2 = 0;
  m_byOA = 0;
  m_byOCA2 = 0;
  m_byDDRA = 0;
  m_byCTLA = 0;
  m_byIRQAState = 0;

  m_byIB = 0;
  m_byCB1 = 0;
  m_byICB2 = 0;
  m_byOB = 0;
  m_byOCB2 = 0;
  m_byDDRB = 0;
  m_byCTLB = 0;
  m_byIRQBState = 0;
}

void C6821::UpdateInterrupts()
{
  unsigned char byNewState;

  // start with IRQ A
  byNewState = 0;
  if ((IRQ1(m_byCTLA) && IRQ1_ENABLED(m_byCTLA)) || (IRQ2(m_byCTLA) && IRQ2_ENABLED(m_byCTLA)))
    byNewState = 1;

  if (byNewState != m_byIRQAState) {
    m_byIRQAState = byNewState;
    PIA_W_CALLBACK(m_stOutIRQA, m_byIRQAState);
  }

  /* then do IRQ B */
  byNewState = 0;
  if ((IRQ1(m_byCTLB) && IRQ1_ENABLED(m_byCTLB)) || (IRQ2(m_byCTLB) && IRQ2_ENABLED(m_byCTLB)))
    byNewState = 1;

  if (byNewState != m_byIRQBState) {
    m_byIRQBState = byNewState;
    PIA_W_CALLBACK(m_stOutIRQB, m_byIRQBState);
  }
}

void C6821::SetCA1(unsigned char byData)
{
  byData = byData ? 1 : 0;

  // the new state has caused a transition
  if (m_byCA1 ^ byData) {
    // handle the active transition
    if ((byData && C1_LOW_TO_HIGH(m_byCTLA)) || (!byData && C1_HIGH_TO_LOW(m_byCTLA))) {
      // mark the IRQ
      SET_IRQ1(m_byCTLA);

      // update externals
      UpdateInterrupts();

      // CA2 is configured as output and in read strobe mode and cleared by a CA1 transition
      if (C2_OUTPUT(m_byCTLA) && C2_STROBE_MODE(m_byCTLA) && STROBE_C1_RESET(m_byCTLA)) {
        // call the CA2 output function
        if (!m_byOCA2)
          PIA_W_CALLBACK(m_stOutCA2, 1);

        // clear CA2
        m_byOCA2 = 1;
      }
    }
  }

  // set the new value for CA1
  m_byCA1 = byData;
}

void C6821::SetCA2(unsigned char byData)
{
  byData = byData ? 1 : 0;

  // CA2 is in input mode
  if (C2_INPUT(m_byCTLA)) {
    // the new state has caused a transition
    if (m_byICA2 ^ byData) {
      // handle the active transition
      if ((byData && C2_LOW_TO_HIGH(m_byCTLA)) || (!byData && C2_HIGH_TO_LOW(m_byCTLA))) {
        // mark the IRQ
        SET_IRQ2(m_byCTLA);

        // update externals
        UpdateInterrupts();
      }
    }
  }

  // set the new value for CA2
  m_byICA2 = byData;
}

void C6821::SetCB1(unsigned char byData)
{
  byData = byData ? 1 : 0;

  // the new state has caused a transition
  if (m_byCB1 ^ byData) {
    // handle the active transition
    if ((byData && C1_LOW_TO_HIGH(m_byCTLB)) || (!byData && C1_HIGH_TO_LOW(m_byCTLB))) {
      // mark the IRQ
      SET_IRQ1(m_byCTLB);

      // update externals
      UpdateInterrupts();

      // CB2 is configured as output and in read strobe mode and cleared by a CA1 transition
      if (C2_OUTPUT(m_byCTLB) && C2_STROBE_MODE(m_byCTLB) && STROBE_C1_RESET(m_byCTLB)) {
        // the IRQ1 flag must have also been cleared
        if (!IRQ1(m_byCTLB)) {
          // call the CB2 output function
          if (!m_byOCB2)
            PIA_W_CALLBACK(m_stOutCB2, 1);

          // clear CB2
          m_byOCB2 = 1;
        }
      }
    }
  }

  // set the new value for CA1
  m_byCB1 = byData;

}

void C6821::SetCB2(unsigned char byData)
{
  byData = byData ? 1 : 0;

  // CA2 is in input mode
  if (C2_INPUT(m_byCTLB)) {
    // the new state has caused a transition
    if (m_byICB2 ^ byData) {
      // handle the active transition
      if ((byData && C2_LOW_TO_HIGH(m_byCTLB)) || (!byData && C2_HIGH_TO_LOW(m_byCTLB))) {
        // mark the IRQ
        SET_IRQ2(m_byCTLB);

        // update externals
        UpdateInterrupts();
      }
    }
  }

  // set the new value for CA2
  m_byICB2 = byData;
}

void C6821::SetPA(unsigned char byData)
{
  m_byIA = byData;
}

void C6821::SetPB(unsigned char byData)
{
  m_byIB = byData;
}

unsigned char C6821::GetPA()
{
  return m_byOA & m_byDDRA;
}

unsigned char C6821::GetPB()
{
  return m_byOB & m_byDDRB;
}
